package com.cmii.sjw.department.control.common;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import jakarta.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * DistributeLock
 *
 * @author niuzhenyu
 * @date 2020/10/26
 * @description 利用redis构造分布式锁
 */
@Slf4j
@Component
public class DistributeLock {

    /**
     * 需要用到redis的序列执行的特点
     */
    @Resource
    private RedisService redisService;

    /**
     * 尝试获取锁的间隔时间 50毫秒
     */
    private int tryInterval = 50;

    /**
     * 内网serviceKey，一个微服务一个key
     */
    private final static String serviceKey = UUID.randomUUID().toString();


    /**
     * 获取锁，成功则返回该锁的过期时间，失败则返回0；
     * 可重入
     *
     * @param lockName     锁名
     * @param retry        获取锁失败后是否重试直到超时
     * @param timeoutMsecs 锁超时时间，等待超时时间，锁必须有超时时间
     * @throws InterruptedException
     */
    public boolean tryLock(String lockName, boolean retry, int timeoutMsecs) {
        if (StringUtils.isEmpty(lockName)) {
            return false;
        }

        lockName = "DISTRIBUTE_LOCK:" + lockName;
        if (!retry) {
            return redisService.setNx(lockName, getRedisValue(), timeoutMsecs, TimeUnit.MILLISECONDS);
        }

        //等待时间
        int waitTime = 0;
        boolean reentry = true;
        String value = getRedisValue();

        while (waitTime <= timeoutMsecs) {
            boolean result = redisService.setNx(lockName, value, timeoutMsecs, TimeUnit.MILLISECONDS);
            if (result) {
                return result;
            } else if (reentry) {
                //如果没有获取到锁，判断是否是重入，只判断一次
                String redisValue = redisService.get(lockName, String.class);
                if (value.equals(redisValue)) {
                    return true;
                } else {
                    reentry = false;
                }
            }

            //停顿后重试
            try {
                Thread.sleep(tryInterval);
            } catch (InterruptedException e) {
                log.error(e.getMessage(), e);
                break;
            }
            waitTime = waitTime + tryInterval;
        }
        return false;
    }

    /**
     * 获取锁，成功则返回该锁的过期时间，失败则返回0；
     * 可重入
     *
     * @param lockName     锁名
     * @param timeoutMsecs 锁超时时间，等待超时时间，锁必须有超时时间
     * @param interval  自定义重试间隔时间
     * @throws InterruptedException
     */
    public boolean tryLock(String lockName, int timeoutMsecs, int interval) {
        if (StringUtils.isEmpty(lockName)) {
            return false;
        }

        lockName = "DISTRIBUTE_LOCK:" + lockName;

        //等待时间
        int waitTime = 0;
        boolean reentry = true;
        String value = getRedisValue();

        while (waitTime <= timeoutMsecs) {
            boolean result = redisService.setNx(lockName, value, timeoutMsecs, TimeUnit.MILLISECONDS);

            if (result) {
                return result;
            } else if (reentry) {
                //如果没有获取到锁，判断是否是重入，只判断一次
                String redisValue = redisService.get(lockName, String.class);
                if (value.equals(redisValue)) {
                    return true;
                } else {
                    reentry = false;
                }
            }

            //停顿后重试
            try {
                Thread.sleep(interval);
            } catch (InterruptedException e) {
                log.error(e.getMessage(), e);
                break;
            }
            waitTime = waitTime + interval;
        }
        return false;
    }

    /**
     * 由服务key+线程id构成，唯一标记一个线程
     *
     * @return
     */
    public String getRedisValue() {
        return serviceKey + "-" + Thread.currentThread().getId();
    }


    /**
     * 询问是否存在锁
     * @param lockName
     * @return
     */
    public boolean isLocked(String lockName){
        lockName = "DISTRIBUTE_LOCK:" + lockName;
        return redisService.isExist(lockName);
    }




    /**
     * 解锁，如果是加锁者 则删除锁
     * 若不是加锁者，静等锁超时
     *
     * @param lockName
     */
    public boolean releaseLock(String lockName) {
        lockName = "DISTRIBUTE_LOCK:" + lockName;
        String value = getRedisValue();
        String redisValue = null;
        try {
            redisValue = redisService.get(lockName, String.class);
            log.debug("distribute lock {}, value is {}", lockName, redisValue);
            if (value.equals(redisValue) || redisValue == null) {
                //如果是加锁者 则删除锁
                redisService.delete(lockName);
                log.info("distribute lock {} has been released!", lockName);
                return true;
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        log.info("distribute lock {} has not been released! redis value is {},server key is {},theadKey is {}", lockName, redisValue, serviceKey, Thread.currentThread().getId());
        return false;
    }

    /**
     * 解锁，如果持有加锁者信息 则删除锁
     * 否则静等锁超时
     *
     * @param lockName
     * @param lockValue 加锁线程的信息
     */
    public boolean releaseLock(String lockName, String lockValue) {
        if (lockValue == null) {
            return false;
        }
        lockName = "DISTRIBUTE_LOCK:" + lockName;
        String redisValue = null;
        try {
            redisValue = redisService.get(lockName, String.class);
            log.debug("distribute lock {}, value is {}", lockName, redisValue);
            //如果值相同，则可以解锁
            if (lockValue.equals(redisValue)) {
                redisService.delete(lockName);
                log.info("distribute lock {} has been released!", lockName);
                return true;
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        log.info("distribute lock {} has not been released! redis value is {},server key is {},theadKey is {}", lockName, redisValue, serviceKey, Thread.currentThread().getId());
        return false;
    }


    /**
     * 强制解锁，应用于MQ隔离了处理开始和结束的流程
     *
     * @param lockName
     */
    public boolean forceReleaseLock(String lockName) {
        if (StringUtils.isEmpty(lockName)) {
            return false;
        }
        lockName = "DISTRIBUTE_LOCK:" + lockName;
        try {
            redisService.delete(lockName);
            log.info("distribute lock {} has been released!", lockName);
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        log.info("distribute lock {} has not been released! server key is {},theadKey is {}", lockName, serviceKey, Thread.currentThread().getId());
        return false;
    }
}

